package com.by.order.service;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Redis分布式限流器
 * 基于令牌桶算法实现，支持高并发场景下的精确限流
 */
@Component
public class RedisRateLimiter {
    // 失败次数统计
    private final Map<String, AtomicInteger> failCount = new ConcurrentHashMap<>();
    // 失败时间缓存
    private final Map<String, Long> failCache = new ConcurrentHashMap<>();
    // 失败缓存时间（毫秒）
    private static final long FAIL_CACHE_TIME = 5000;

    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    public RedisRateLimiter(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 尝试获取令牌 - 默认获取1个令牌
     *
     * @param key              限流key
     * @param maxPermits       最大令牌数
     * @param permitsPerSecond 每秒生成令牌数
     * @return true-获取成功，false-获取失败
     */
    public boolean tryAcquire(String key, long maxPermits, long permitsPerSecond) {
        return tryAcquire(key, maxPermits, permitsPerSecond, 1);
    }

    /**
     * 尝试获取令牌
     *
     * @param key              限流key
     * @param maxPermits       最大令牌数
     * @param permitsPerSecond 每秒生成令牌数
     * @param permits          需要获取的令牌数
     * @return true-获取成功，false-获取失败
     */
    public boolean tryAcquire(String key, long maxPermits, long permitsPerSecond, int permits) {
        if (isKeyFailed(key)) {
            return tryLocalAcquire(key, maxPermits, permitsPerSecond, permits);
        }


        String script = """
                local key = KEYS[1]
                 local maxPermits = tonumber(ARGV[1])
                 local permitsPerSecond = tonumber(ARGV[2])
                 local permits = tonumber(ARGV[3])
                 local currentTime = tonumber(ARGV[4])
                
                 if not maxPermits or not permitsPerSecond or not permits or not currentTime then
                     return 0
                 end
                
                 local data = redis.call('HMGET', key, 'lastRefillTime', 'tokens')
                 local lastRefillTime = tonumber(data[1])
                 local tokens = tonumber(data[2])
                
                 if not lastRefillTime or not tokens then
                     lastRefillTime = currentTime
                     tokens = maxPermits - permits
                     if tokens < 0 then tokens = 0 end
                     redis.call('HMSET', key, 'lastRefillTime', tostring(lastRefillTime), 'tokens', tostring(tokens))
                     redis.call('EXPIRE', key, 3600)
                     return 1
                 end
                
                 local timePassed = currentTime - lastRefillTime
                 local newTokens = tokens + (timePassed * permitsPerSecond)
                 newTokens = math.min(maxPermits, newTokens)
                
                 if newTokens >= permits then
                     newTokens = newTokens - permits
                     redis.call('HMSET', key, 'lastRefillTime', tostring(currentTime), 'tokens', tostring(newTokens))
                     return 1
                 else
                     redis.call('HMSET', key, 'lastRefillTime', tostring(currentTime), 'tokens', tostring(newTokens))
                     return 0
                 end
                """;

        try {
            long currentTimeSeconds = System.currentTimeMillis() / 1000;

            Object result = executeLuaScript(script, Collections.singletonList(key),
                    maxPermits, permitsPerSecond, permits, currentTimeSeconds);

            // 清除失败缓存
            failCache.remove(key);
            failCount.remove(key);

            return result != null && "1".equals(result.toString());
        } catch (Exception e) {
            recordFailure(key);
            return tryLocalAcquire(key, maxPermits, permitsPerSecond, permits);
        }
    }

    /**
     * 执行Lua脚本
     *
     * @param script Lua脚本
     * @param keys   键列表
     * @param args   参数列表
     * @return 脚本执行结果
     */
    private Object executeLuaScript(String script, List<String> keys, Object... args) {
        String[] stringArgs = new String[args.length];
        for (int i = 0; i < args.length; i++) {
            stringArgs[i] = args[i] != null ? args[i].toString() : "";
        }

        return redisTemplate.execute((RedisCallback<Object>) connection -> {
            // 使用原生连接执行Lua脚本
            byte[][] keyBytes = keys.stream()
                    .map(k -> k.getBytes(StandardCharsets.UTF_8))
                    .toArray(byte[][]::new);

            byte[][] argBytes = Arrays.stream(stringArgs)
                    .map(a -> a.getBytes(StandardCharsets.UTF_8))
                    .toArray(byte[][]::new);

            // 使用EVAL命令执行Lua脚本，根据脚本返回值类型确定ReturnType
            return connection.eval(
                    script.getBytes(StandardCharsets.UTF_8),
                    ReturnType.INTEGER,  // 使用INTEGER类型处理返回值，因为Lua脚本返回的是数字类型(1或0)
                    keyBytes.length,
                    concat(keyBytes, argBytes)
            );
        });
    }

    // 辅助方法：合并两个字节数组
    private byte[][] concat(byte[][] a, byte[][] b) {
        byte[][] result = new byte[a.length + b.length][];
        System.arraycopy(a, 0, result, 0, a.length);
        System.arraycopy(b, 0, result, a.length, b.length);
        return result;
    }

    /**
     * 准备脚本执行的参数列表
     *
     * @param maxPermits       最大令牌数
     * @param permitsPerSecond 每秒生成令牌数
     * @param permits          需要获取的令牌数
     * @param currentTime      当前时间戳
     * @return 参数列表
     */
    private List<String> prepareScriptArgs(long maxPermits, long permitsPerSecond, int permits, long currentTime) {
        return Arrays.asList(
                String.valueOf(maxPermits),
                String.valueOf(permitsPerSecond),
                String.valueOf(permits),
                String.valueOf(currentTime)
        );
    }

    /**
     * 检查key是否在失败缓存中
     *
     * @param key 限流键
     * @return true-在失败缓存中，false-不在失败缓存中
     */
    private boolean isKeyFailed(String key) {
        Long failTime = failCache.get(key);
        if (failTime == null) {
            return false;
        }
        // 检查是否还在缓存时间内
        return System.currentTimeMillis() - failTime < FAIL_CACHE_TIME;
    }

    /**
     * 记录key的失败次数
     *
     * @param key 限流键
     */
    private void recordFailure(String key) {
        failCount.computeIfAbsent(key, k -> new AtomicInteger(0)).incrementAndGet();
        failCache.put(key, System.currentTimeMillis());
    }

    /**
     * 本地令牌桶限流实现（降级方案）
     *
     * @param key              限流key
     * @param maxPermits       最大令牌数
     * @param permitsPerSecond 每秒生成的令牌数
     * @param permits          需要获取的令牌数
     * @return true-获取成功，false-获取失败
     */
    private boolean tryLocalAcquire(String key, long maxPermits, long permitsPerSecond, int permits) {
        // 使用本地的简单限流机制
        // 这里可以使用一个简化的令牌桶实现或者直接按时间窗口限流

        // 简单实现：按时间窗口限流，每秒最多允许permitsPerSecond个请求
        long currentTime = System.currentTimeMillis();
        String localKey = "local_rate_limit:" + key;

        // 使用Redis存储本地限流信息（避免在JVM本地存储状态导致不一致）
        try {
            String script = """
                    local key = KEYS[1]
                    local maxRequests = tonumber(ARGV[1])
                    local windowSize = tonumber(ARGV[2]) -- 窗口大小（毫秒）
                    local currentTime = tonumber(ARGV[3])
                    
                    local windowKey = key .. ":window"
                    local countKey = key .. ":count"
                    
                    local lastWindow = redis.call('GET', windowKey)
                    local currentCount = redis.call('GET', countKey)
                    
                    if lastWindow == false or currentCount == false then
                        -- 初始化
                        redis.call('SET', windowKey, currentTime)
                        redis.call('SET', countKey, 1)
                        redis.call('EXPIRE', windowKey, math.ceil(windowSize / 1000))
                        redis.call('EXPIRE', countKey, math.ceil(windowSize / 1000))
                        return 1
                    else
                        lastWindow = tonumber(lastWindow)
                        currentCount = tonumber(currentCount)
                    
                        -- 检查是否在同一个时间窗口内
                        if currentTime - lastWindow < windowSize then
                            -- 同一窗口内，检查是否超过限制
                            if currentCount >= maxRequests then
                                return 0
                            else
                                -- 增加计数
                                redis.call('INCR', countKey)
                                return 1
                            end
                        else
                            -- 新窗口，重置计数
                            redis.call('SET', windowKey, currentTime)
                            redis.call('SET', countKey, 1)
                            redis.call('EXPIRE', windowKey, math.ceil(windowSize / 1000))
                            redis.call('EXPIRE', countKey, math.ceil(windowSize / 1000))
                            return 1
                        end
                    end
                    """;

            Object result = executeLuaScript(script, Collections.singletonList(localKey),
                    maxPermits, // maxRequests
                    1000L,      // windowSize (1秒)
                    currentTime);

            return result != null && "1".equals(result.toString());
        } catch (Exception e) {
            e.printStackTrace();
            return true;
        }
    }
}